package com.guo.mixframe.framework.utils

import java.io.File
import java.util.function.Predicate
import java.util.jar.JarFile

import kotlin.reflect.KClass

/**
 * @author  gx
 * @description
 */
object PackageUtils {

  fun getClassesByAnnotation(packName: String, classType: KClass<out Annotation>): List<KClass<*>> {
    return getClasses(packName) {
      try {
        it.annotations.any{annotation -> classType == annotation.annotationClass }
      }catch (exception : UnsupportedOperationException){
        false
      } catch (throwable:Throwable){
        false
      }
    }
  }

  /**
   * 获取包下的所有类并且通过predicate进行筛选
   */
  private fun getClasses(packName: String, predicate: Predicate<KClass<*>>): List<KClass<*>> {
    val classesNames = getClassNames(packName)
    return classesNames.asSequence()
      .map { Class.forName(it,false,PackageUtils::class.java.classLoader) }
      .map { it.kotlin }
      .filter { predicate.test(it) }
      .toList()
  }

  private fun getClassNames(packName: String): List<String> {
    val packagePath = packName.replace(".", "/")
    val urls = PackageUtils::class.java.classLoader.getResources(packagePath) ?: return listOf()
    val classNames = mutableListOf<String>()
    for (url in urls) {
      classNames.addAll(
        when (url.protocol) {
          "file" -> getClassNamesByFile(url.path, packName)
          "jar" -> getClassNamesByJar(url.path)
          else -> listOf()
        }
      )
    }
    return classNames
  }

  private fun getClassNamesByJar(path: String): List<String> {
    val jarInfo = path.split("!").toTypedArray()
    val jarFilePath = jarInfo[0].substring(jarInfo[0].indexOf("/"))
    val packagePath = jarInfo[1].substring(1)
    val jarFile = JarFile(jarFilePath)
    val entries = jarFile.entries()
    return entries.asSequence()
      .filter { it.name.endsWith(".class") }
      .filter {
        val index = it.name.lastIndexOf(("/"))
        val classPath = if(index != -1){
          it.name.substring(0,index)
        }else{
          it.name
        }
        classPath.startsWith(packagePath)
      }
      .map { getClassName(it.name) }
      .toList()
  }

  private fun getClassNamesByFile(packPath: String?, packName: String): List<String> {
    val classNames = mutableListOf<String>()
    val fileNode = File(packPath)
    val childFiles = fileNode.listFiles()?: return classNames
    for(childFile in childFiles){
      val childFileName = childFile.name
      if(childFile.isDirectory){
        classNames.addAll(getClassNamesByFile(childFile.path,"$packName.$childFileName"))
      }else{
        if(childFileName.endsWith(".class")){
          classNames.add("$packName.${getClassName(childFileName)}")
        }
      }
    }
    return classNames
  }

  private fun getClassName(path:String):String{
    return path.substring(0,path.lastIndexOf(".")).replace("/".toRegex(),".")
  }

}
